Pinvon's Blog

所见, 所闻, 所思, 所想

正则表达式及 grep sed awk 工具

正则表达式

元字符

\b, \B, ^, $

\b 匹配单词的开头或结尾, 即单词的分界处, 英文的单词通常用空格, 标点符号和换来来分隔, 但 \b 不匹配这些, 它只匹配一个位置.

\B 匹配不是单词开头或结束的位置.

如, 想在一段文本中, 查找 'hi' 这个单词, 一般会把 history 这种单词也匹配进来, 如果使用 \bhi\b, 就可以只匹配 hi 这个单词.

^ 和 $ 也匹配一个位置, ^ 匹配要查找的字符串的开头, $ 匹配结尾. 如果有设置处理多行, 则 ^ 和 $ 也表示一行的开始处和结尾处.

如, 要输入 QQ 号码, QQ 号码有 5-12 位, 可以使用 ^\d{5,12}$

.

. 匹配 除换行符以外 的任何字符.

表示重复的元字符: *, +, ?, {n}, {n,}, {n,m}

  • 匹配的是数量. 表示 * 前边的内容可以连续重复使用任意次以使整个表达式得到匹配.

所以, .* 表示任意数量的不包含换行的字符. \bhi\b.*Lucy\b: 先是一个单词 hi, 然后是任意个字符, 但不能是换行, 最后是 Lucy.

  • 也匹配数量, 但 * 可以匹配任意次(包括 0 次), 而 + 则匹配重复 1 次或更多次.

? 重复 0 次或 1 次.

{n} 重复 n 次.

{n,} 重复 n 次或更多次.

{n,m} 重复 n 到 m 次.

\d, \s, \w, \D, \ S, \W

\d 匹配一位数字, 等价于 [0-9]. 如果想匹配 3 位连续的数字, 可以写成: \d\d\d, 也可以写成: \d{3}

\s 匹配任意的空白符, 包括空格, Tab, 换行符, 中文全角空格等.

\w 匹配字母, 数字, 下划线. 等价于 '[A-Za-z0-9_]'

例子: \ba\w*\b, 匹配任意以字母 a 开头的单词, 首先是单词开始处 \b, 然后是字母 a, 然后是任意数量的字母或数字 \w*, 最后是单词的结束 \b.

最后, \D, \ S, \W 都是 \d, \s, \w 的反义词, 如, \D 表示任意非数字的字符.

\

\ 表示转义符, 比如想查找 . 或 *, 由于它们是元字符, 会被解释成其他意义. \. 可以表示字符 ., 如果想查找 \ 本身, 就使用 \\.

方括号 []

[] 里面写的内容, 可以匹配 一个字符.

[aeiou] 可以匹配 a 或 e 或 i 或 o 或 u 这五个字符中的一个;

[.?!] 可以匹配标点符号 . 或 ? 或 !

\(?0\d{2}[) -]?\d{8} 可以匹配几种格式的电话号码, 如 (010)88886666, 022-22334455, 02912345678. 首先是转义字符 \(, ? 表示 \( 可以出现 0 次或 1 次; 然后是数字 0; 然后是任意的两个数字 \d{2}, 然后是 ) 或 - 或空格 [) -], 可以出现 0 次或 1 次; 最后是 8 个数字 \d{8}.

在 [] 内部使用 ^ 表示取反, 如: [^aeiou] 表示除 aeiou 这五个字母之外的任意字符.

分枝条件

\(?0\d{2}[) -]?\d{8} 也可以匹配到 (010-87654321 这种不正确的格式.

分枝条件是指, 在正则表达式中写好几种规则, 满足其中一种规则就可以匹配, 几种规则使用 | 分隔.

如: 0\d{2}-\d{8}|0\d{3}-\d{7} 可以匹配两种以 - 分隔的电话号码, 一种是三位区号, 8 位本地号, 另一种是 4 位区号, 7 位本地号.

\d{5}-\d{4}|\d{5} 可以匹配美国的邮政编码. 美国邮编的规则是 5 位数字, 或者用 - 连接的 9 位数字. 但不能改成 \d{5}|\d{5}-\d{4}, 因为 使用分枝条件时, 会从左到右测试每个条件, 如果满足了某个分枝, 就不会再去管其他的条件了. 所以这边, 只会用到前一个分枝, 只能匹配 5 位的邮编, 或者 9 位邮编中的前 5 位.

分组

() 可以用来指定子表达式(也叫分组). 如果想要重复多个字符, 就可以用小括号来指定子表达式, 然后指定这个子表达式的重复次数.

匹配 IP 的简单表达式: (\d{1,3}\.){3}\d{1,3} 解析: \d{1,3} 匹配 1 到 3 位数字, (\d{1,3}\.){3} 匹配三位数字加上一个英文句号, 然后这个整体重复 3 次, 最后再加上一个 1 到 3 位的数字 \d{1,3}

但是, 这个表达式还可以匹配 256.300.999.888 这样的不可能存在的 IP, 所以要改成: ((2[0-4]\d|25[0-5]|1?\d\d?\.){3}(2[0-4]\d|25[0-5]|1?\d\d?)

后向引用

后向引用是指, 前一个子表达式匹配的内容, 可以传递给后一个子表达式作进一步处理. 以分组的左括号为标志, 第一个出现的分组组号为 1, 写成 \1, 第二个出现的分组组号为 2, 写成 \2.

\b(\w+)\b\s+\1\b 的作用是匹配重复出现的单词, 比如 go go. 这个表达式首先是一个词, 这个词有一或多个的字母, 数字或下划线 \b(\w+)\b. 这个词会被捕获到编号为 1 的分组中, 然后是 1 个或几个空白符 (\s+), 最后是分组 1 中捕获的内容 \1.

指定分组名字

我们可以自己指定分组的名字, 语法如下:

(?<name>exp)

# 要使用时
\k<name>

上面的例子也可以写成: \b(?<word>\w+)\b\s+\k<word>\b

分组的小结

(exp): 匹配 exp, 并捕获文本到自动命名的组里

(?<name>exp): 匹配 exp, 并捕获文本到名称为 name 的分组中, 也可以写成: (?'name'exp)

(?:exp): 匹配 exp, 但不捕获匹配的广西, 也不给此分组分配组号

(?=exp): 匹配 exp 前面的位置

(?<=exp): 匹配 exp 后面的位置

(?!exp): 匹配后面跟的不是 exp 的位置

(?<!exp): 匹配前面不是 exp 的位置

(?#comment): 注释

零宽断言

断方用来声明一个应该为真的事实. 正则表达式中, 只有当断言为真时, 才会继续进行匹配.

零宽度正预测先行断言 (?=exp)

(?=exp) 断言自身出现的位置的后面能匹配表达式 exp. 如: \b\w+(?=ing\b) 表示匹配以 ing 结尾的单词的前面部分, 当文本为 I'm singing while you're dancing 时, 可以匹配到 sing 和 danc.

零宽度正回顾后发断言 (?<=exp)

(?<=exp) 断言自身出现的位置的前面能匹配表达式 exp. 如: (?<=\bre)\w+\b 能匹配以 re 开头的单词的后半部分, 当文本为 reading a book 时, 它匹配 ading.

零宽度负预测先行断言 (?!exp)

(?!exp) 断言此位置的后面不能匹配表达式 exp.

如, \d{3}(?!\d) 匹配三位数字, 而且这三位数字的后面不能是数字.

如, \b((?!abc)\w)+\b 匹配不包含连续字符串 abc 的单词.

零宽度负回顾后发断言 (?<!exp)

(?<!exp) 断言此位置的前面不能匹配表达式 exp.

如, (?<![a-z])\d{7} 匹配前面不是小写字母的 7 位数字.

复杂的例子

(?<=<(\w+)>).*(?=<\/\1>) 匹配 HTML 标签内的内容.

<(\w+)>: 匹配被 <> 括起来的单词, 包括 <>, 用 \1 来表示.

(?<=<(\w+)>): 匹配 <(\w+)> 后面的部分, 不包括 <(\w+)>

.*: 匹配任意长度的字符.

<\/\1>: \1 表示分组 1, 在前面匹配到的, \/ 中, \ 是转义符, 最终表示字符 /, 所以假如 \1 为 html, 则相当于 </html>

(?=<\/\1>): 匹配 <\/\1> 前面的部分, 不包括 <\/\1>

懒惰匹配

正则表达式默认是贪婪匹配. 如果要改成懒惰匹配, 可以在限定符后面加 ?

*? : 重复任意次, 但尽可能少重复
+? : 重复 1 次或更多次, 但尽可能少重复
?? : 重复 0 次或 1 次, 但尽可能少重复
{n,m}? : 重复 n 到 m 次, 但尽可能少重复
{n,}? : 重复 n 次以上, 但尽可能少重复

grep

语法

grep 用来一行一行分析数据. 其语法如下:

grep [-a,-c,-i,-n,-v] [--color=auto] '查找字符串' filename

-a: 将 binary 文件以 text 文件的方式查找数据;
-c: 计算 '查找字符串' 出现的次数;
-i: 忽略大小写;
-n: 输出行号;
-v: 反向选择, 即找出不包含 '查找字符串' 的那一行
--color=auto: 将 '查找字符串' 加上颜色显示

基本用法

last | grep 'pinvon'  # 将 last 当中, pinvon 用户登录的那些行显示.
last | grep -v 'pinvon'  # 将 last 当中, 不是 pinvon 用户登录的那些行显示
grep --color=auto 'MANPATH' /etc/man.config  # 将 /etc/man.config 内包含 MANPATH 的那些行显示

如果想每次使用 grep 时, 相关信息都用颜色表示, 就用 alias 设置别名:

alias grep='grep --color=auto'

配合正则表达式

grep -n 'o\{2\}' regular_express.txt  # 在 regular_express.txt 中找到有两个 o 的行

ls | grep -n '^a.*'  # 以 a 开头的文件名

sed

sed 是一个管道命令, 可以利用 script 来处理文本文件.

基本语法:

sed [-nefr] [动作]
-n: 使用安静模式, 仅显示被处理后的结果
-e: 在命令行模式中进行 sed 的动作编辑
-f: 直接将 sed 的动作写在一个文件内, -f filename 则可以执行 filename 内的 sed 动作
-r: 使用正则表达式
-i: 直接修改读取的文件内容, 而不是由屏幕输出

# 动作说明: [n1[,n2]]function
[n1[,n2]]: 可选, 表示要处理的行数.

# function 有下面这些参数
a: 新增, 后接要增加的字符串
c: 替换, 后接用于替换的字符串
d: 删除
i: 插入, 后接要插入的字符串
p: 打印, 将某个选择的数据打印出来
s: 替换, 可以搭配正则表达式 sed 's/要被替换的字符串/新的字符串/g'

使用

  1. 将 filename 里面的内容列出并打印行号, 同时, 将第 2~5 行删除
nl filename | sed -e '2,5d'

由于 -e 的意思是在命令行上进行 sed 的编辑动作, 所以对 filename 这个文件本身不产生影响. -e 是默认的, 可不加.

  1. 将 filename 里面的内容列出并打印行号, 同时, 在第 2 行后面增加两行字
nl filename | sed '2a add line 1 \nadd line 2'

如果要在第 2 行之前插入数据, 则把 a 改成 i.

  1. 将 filename 里面的内容列出并打印行号, 同时, 将第 2~5 行的内容替换
nl filename | sed '2,5c test'
  1. 打印 filename 里面的 第 2~5 行内容
nl filename | sed -n '2,5p'

这边使用 -n, 是因为如果不加, 会把所有的内容都输出, 并且第 2~5 行的内容会输出两次. 而加上 -n 后, 只输出第 2~5 行的内容, 其他内容不输出.

  1. 假设 filename 中的注释符号是 #, 使用 sed 命令删除 filename 中的所有注释
cat filename | sed 's/#.*$//g'

awk

awk 和 sed 类似, 也可以处理文件. sed 常把一行当作一个单元来处理, 而 awk 则把一行分成几个字段, 对字段进行处理.

语法如下:

awk '条件类型 1{动作 1} 条件类型 2{动作 2} ...' filename

默认的字段分隔符是空格或 Tab.

实例

使用 last 可以将登录者的数据取出来, 如果只想取出其中的账号和 IP, 且账号与 IP 之间以 Tab 隔开, 则可以这样:

last -n 5 -i | awk '{print $1 "\t" $3}'

awk 有几个内置变量, NF 表示每行拥有的字段总数, NR 表示当前处理的是第几行, FS 表示默认的分隔符是什么.

last -n 5 -i | awk '{print $1 "\t lines: " NR "\t columns: " NF}'

查看第 3 列小于 10 的数据:

cat filename | awk '$3 < 10 {print $1 "\t" $3}'

设置其他的分隔符:

cat filename | awk 'BEGIN {FS=";"} $3 < 10 {print $1 "\t" $3}'

awk 的动作内部, 也支持 if 语句, 如:

cat filename | awk '{if ($3 < 10) {print $1 "\t" $3}'

Footnotes:

1

DEFINITION NOT FOUND.

Comments

使用 Disqus 评论
comments powered by Disqus